package org.eraser.gateway.dynamic;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Objects;


/**
 * 执行
 * spring提供的ApplicationEventPublisherAware事件感知
 * <p></p>
 * 路由定义 RouteDefinition 和 路由 Route 是两个概念
 * <p></p>
 * RouteDefinition 存储在 RouteDefinitionRepository 中
 * <p></p>
 * Route 是网关在运行时要执行的"路由动作"
 * <p></p>
 * RouteLocator 将 RouteDefinition 转换为 Route
 */
@Slf4j
@Service
public class GatewayRouteService implements ApplicationEventPublisherAware {

    /**
     * 写路由定义
     */
    private final RouteDefinitionWriter routeDefinitionWriter;

    /**
     * 获取路由定义
     */
    private final RouteDefinitionLocator routeDefinitionLocator;

    /**
     * 事件发布器 用来发布spring cloud帮我们实现的 RefreshRoutesEvent事件。而事件是由 RouteRefreshListener 监听并执行。本质上还是利用spring提供的事件监听机制触发。
     */
    private ApplicationEventPublisher publisher;

    public GatewayRouteService(RouteDefinitionWriter routeDefinitionWriter, RouteDefinitionLocator routeDefinitionLocator) {
        this.routeDefinitionWriter = routeDefinitionWriter;
        this.routeDefinitionLocator = routeDefinitionLocator;
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        // 完成事件推送句柄的初始化
        this.publisher = applicationEventPublisher;
    }


    /**
     * 根据路由id删除 RouteDefinition
     */
    private void deleteById(String id) {
        try {
            log.info("gateway delete route id: {}", id);
            this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
            // 发布事件通知给 gateway 更新 Route
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
        } catch (Exception ex) {
            log.error("gateway delete route fail: {}", ex.getMessage(), ex);
        }
    }

    /**
     * @return 返回当前网关拥有的 RouteDefinition
     */
    public List<RouteDefinition> getRoutes() {
        return Objects.requireNonNull(routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst());
    }


    /**
     * 增加路由定义
     */
    public void addRouteDefinition(RouteDefinition definition) {
        log.info("gateway add route: {}", definition);

        // 保存 RouteDefinition 这是一个异步操作 会立即返回
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        // publishEvent(new RefreshRoutesEvent(this)) 发布 RefreshRoutesEvent 事件, 通知网关刷新 Route
        // 这两行代码是并行执行的
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    /**
     * 删除掉内存中现有的路由
     *
     * @param oldDefinition 老的路由
     */
    public void deleteByRouteDefinition(RouteDefinition oldDefinition) {

        try {
            // 清除掉之前所有的"旧的"路由定义
            log.info("gateway delete old route: {}", oldDefinition);
            this.routeDefinitionWriter.delete(Mono.just(oldDefinition.getId())).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }


    /**
     * 更新路由
     *
     * @param definitions 从nacos来的路由
     */
    public void updateList(List<RouteDefinition> definitions) {
        log.info("gateway update new route from nacos: {}", definitions);

        // 先拿到当前Gateway 中存储的路由定义
        List<RouteDefinition> routeDefinitionsExits = routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst();

        if (!CollectionUtils.isEmpty(routeDefinitionsExits)) {
            // 删除旧的
            routeDefinitionsExits.forEach(this::deleteByRouteDefinition);
            // 插入新的
            definitions.forEach(this::addRouteDefinition);
        } else {
            // 如果当前Gateway里没有路由
            definitions.forEach(this::addRouteDefinition);
        }

    }


}
